package com.hunktimes.wholesale.widget

import android.graphics.Rect
import android.util.SparseArray
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView

/**
 * Author: Delete
 * Date: 2020/7/27 11:51
 * Description:
 *
 */
class FlowLayoutManager : RecyclerView.LayoutManager {
    val mItemRects: SparseArray<Rect>//key:View的position,保存View的bounds 和显示标志

    private var mVerticalOffset: Int = 0//竖直偏移量,每次换行时候,要根据这个offset判断
    private var mFirstVisiPos: Int = 0//屏幕可见的第一个View的position
    private var mLastVisiPos = 0//屏幕可见的最后一个View的position

    override fun isAutoMeasureEnabled(): Boolean = true

    constructor() : super() {
        mItemRects = SparseArray()

    }

    override fun generateDefaultLayoutParams(): RecyclerView.LayoutParams =
        RecyclerView.LayoutParams(
            ViewGroup.MarginLayoutParams.WRAP_CONTENT,
            ViewGroup.MarginLayoutParams.WRAP_CONTENT
        )

    /**
     * 该方法是LayoutManager的入口。它会在如下情况下被调用：
     * 1 在RecyclerView初始化时，会被调用两次。
     * 2 在调用adapter.notifyDataSetChanged()时，会被调用。
     * 3 在调用setAdapter替换Adapter时,会被调用。
     * 4 在RecyclerView执行动画时，它也会被调用。
     * 即RecyclerView 初始化 、 数据源改变时 都会被调用。
     * @param recycler
     * @param state
     */
    override fun onLayoutChildren(recycler: RecyclerView.Recycler, state: RecyclerView.State) {
        if (itemCount == 0) {//没有item 界面空着
            detachAndScrapAttachedViews(recycler)
            return
        }
        if (itemCount == 0 && state.isPreLayout) {//state.isPreLayout()是支持动画的
            return
        }
        //onLayoutChildren方法在RecyclerView初始化時
        detachAndScrapAttachedViews(recycler)
        //初始化
        mVerticalOffset = 0
        mFirstVisiPos = 0
        mLastVisiPos = itemCount//adapter中绑定的数据的个数
        //初始化时调用,填充 childView

        //初始化时调用,填充 childView
        fill(recycler, state)
    }

    /**
     * 初始化时调用,填充childView
     *
     * @param recycler
     * @param state
     */
    private fun fill(recycler: RecyclerView.Recycler, state: RecyclerView.State) {
        fill(recycler, state, 0)
    }

    /**
     * 填充childViwe的核心犯法,应该线填充,再移动,
     * 在填充时候,预先计算dy的在内,如果view越界,则回收掉,
     * 一般情况是返回dy,如果出现View数量不足,则返回修正后的 dy
     *
     * @param recycler
     * @param state
     * @param dy       修正之后,真正的 dy,可能剩余空间不够移动那么多了,所以 return < \dy\
     */
    private fun fill(recycler: RecyclerView.Recycler, state: RecyclerView.State, dy: Int):Int {
        var topOffset = paddingTop //布局时的上偏移
        var dy = dy
        //回收越界子view,同时 处理首次加载完成可显示item 手指滑动,有新item进来,旧item出去的时候 的处理
        if (childCount > 0) { //滑动时 进来的
            for (i in childCount - 1 downTo 0) { //getChildCount-->显示出来的item个数
                val child = getChildAt(i)
                if (dy > 0) { //需要回收 当前屏幕,上越界的View
                    if (getDecoratedBottom(child!!) - dy < topOffset) {
                        // getDecoratedBottom(child)-->item底边距屏幕上沿距离
                        removeAndRecycleView(child!!, recycler)
                        mFirstVisiPos++
                        continue
                    }
                } else if (dy < 0) { //回收当前屏幕,下越界的View
                    if (getDecoratedTop(child!!) - dy > height - paddingBottom) {
                        //此时dy 是负值,手指下滑
                        //getDecoratedTop(child) - dy > getHeight() --> 得到的是最后一个item 的上边界 + 滑动距离
                        //如果超出 屏幕范围 就回收
                        //注意: 此时dy 是负值,
                        // item的底边距屏幕上沿距离 + 滑动距离 超出RecyclerView的高度,则回收
                        removeAndRecycleView(child!!, recycler)
                        mLastVisiPos--
                        continue
                    }
                }
            }
        }

        var leftOffset = paddingLeft //布局时的左偏移

        var lineMaxHeight = 0 //每一行最大的高度

        //布局子View阶段
        //布局子View阶段
        if (dy >= 0) {
            //初始化时，我们不清楚究竟要layout多少个子View，所以就假设从0~itemcount-1
            var minPos: Int = mFirstVisiPos
            mLastVisiPos = itemCount - 1 //getItemCount-->适配器中绑定的item个数
            if (childCount > 0) { //getChildCount-->显示出来的item个数
                val lastView =
                    getChildAt(childCount - 1) //lastView-->最后一个可见的item
                minPos =
                    getPosition(lastView!!) + 1 //从 最后一个View+1 开始 getPosition --> adapter中的position
                topOffset = getDecoratedTop(lastView!!)
                leftOffset =
                    getDecoratedRight(lastView!!) //????此处有疑问:minPos 后来个人思考认为: minPos 最后一个可见的item的位置
                //后来 想明白,是这样: 这个if里 处理了已经有部分child 显示出来了,那么接下来addChild
                //要从 最后一个显示出来的 chid开始即,minPos的位置 getPosition --> adapter中 数据的位置
                //这个if的作用 是修正 顶部偏移 左部偏移 和 开始addChild的位置即minPos
                //getDecorateRight 是从最后一个child 的右侧开始 接续添加item

                //error: 出现 新一行层叠的错误 是这里误写成 min
                lineMaxHeight =
                    Math.max(lineMaxHeight, getDecoratedMeasurementVertical(lastView!!))
            }
            //顺序 addChildView
            for (i in minPos..mLastVisiPos) { //个人认为,这些都是不可见的,不懂为啥还要layout --> 后来发现这个想法是错误的
                // 后来debug 发现 第一次的时候getChildCount= 0 因为此时买么有view attach 到rv上,上面的if不走
                //找recycler要一个childItemView,
                // 我们不管他是从scrp里去,
                // 还是从reccyclerViewPool里取,
                // 亦或是onCreateViewHolder里拿到
                val child = recycler.getViewForPosition(i)
                addView(child)
                measureChildWithMargins(child, 0, 0)
                //计算宽度,包括margin
                if (leftOffset + getDecoratedMeasurementHorizontal(child) <= getHorizontalSpace()) { //当前行还排列的下 未满
                    layoutDecoratedWithMargins(
                        child, leftOffset, topOffset,
                        leftOffset + getDecoratedMeasurementHorizontal(child),
                        topOffset + getDecoratedMeasurementVertical(child)
                    )

                    //保存Rect供逆序layout用
                    val rect = Rect(
                        leftOffset,
                        topOffset + mVerticalOffset,
                        leftOffset + getDecoratedMeasurementHorizontal(child),
                        topOffset + getDecoratedMeasurementVertical(child) + mVerticalOffset
                    )
                    mItemRects.put(i, rect)


                    //更新 leftOffset  lineHeight
                    leftOffset += getDecoratedMeasurementHorizontal(child)
                    lineMaxHeight =
                        Math.max(lineMaxHeight, getDecoratedMeasurementVertical(child))
                } else { //当前行已满 排列不下了
                    //更新 left top lineHeight
                    leftOffset = paddingLeft
                    topOffset += lineMaxHeight
                    lineMaxHeight = 0

                    //新起一行的时候要判断一下边界
                    if (topOffset - dy > height - paddingBottom) {
                        //越界了,就回收
                        removeAndRecycleView(child, recycler)
                        mLastVisiPos = i - 1
                    } else { //总高度 没超出 屏幕范围
                        layoutDecoratedWithMargins(
                            child,
                            leftOffset, topOffset,
                            leftOffset + getDecoratedMeasurementHorizontal(child),
                            topOffset + getDecoratedMeasurementVertical(child)
                        )

                        //保存Rect供逆序layout用
                        val rect = Rect(
                            leftOffset,
                            topOffset + mVerticalOffset,
                            leftOffset + getDecoratedMeasurementHorizontal(child),
                            topOffset + getDecoratedMeasurementVertical(child) + mVerticalOffset
                        )
                        mItemRects.put(i, rect)

                        //改变 left lineHeight
                        leftOffset += getDecoratedMeasurementHorizontal(child)
                        lineMaxHeight = Math.max(
                            lineMaxHeight,
                            getDecoratedMeasurementVertical(child)
                        )
                    }
                }
            }
            //按顺序 addView 之后,
            // 判断是否已经没有更多的ItemView,并且,如果此时屏幕仍有空白,
            //则需要修正dy

            //getChildCount-->显示出来的item个数
            //getItemCount-->返回绑定到父RecyclerView的适配器中的项目数。即总的数据源的个数
            val lastChild = getChildAt(childCount - 1)
            if (getPosition(lastChild!!) == itemCount - 1) { //
                val gap = height - paddingBottom - getDecoratedBottom(lastChild!!)
                if (gap > 0) {
                    dy -= gap //滑动距离不能 完全消耗consume 需要减去 多出的部分,滑回去
                }
            }
        } else { //dy < 0 的情况 个人认为 这个else 里处理的是复用的情况 dy<0 -->手指向下滑
            //利用Rect 保存子View 的边界
            //正序排列时,保存每个子View的Rect,逆序时直接拿出来Layout
            var maxPos = itemCount - 1
            mFirstVisiPos = 0
            if (childCount > 0) {
                val firstView = getChildAt(0)
                maxPos = getPosition(firstView!!) - 1
            }
            for (i in maxPos downTo mFirstVisiPos) {
                val rect = mItemRects[i]
                if (rect.bottom - mVerticalOffset - dy < paddingTop) {
                    //上滑时,底边-竖直位置-滑动距离 <RvView的上padding 表明: 已经滑出上边界
                    mFirstVisiPos = i + 1
                    break
                } else {
                    val child = recycler.getViewForPosition(i)
                    addView(child, 0)
                    measureChildWithMargins(child, 0, 0)
                    layoutDecoratedWithMargins(
                        child,
                        rect.left,
                        rect.top - mVerticalOffset,
                        rect.right,
                        rect.bottom - mVerticalOffset
                    )
                }
            }
        }
        return dy
    }
    /**
     * @param dy       recyclerView 滑动距离
     * @param recycler
     * @param state
     * @return 返回值会被RecyclerView用来判断是否达到边界，
     * 如果返回值！=传入的dy，则会有一个边缘的发光效果，表示到达了边界。
     * 而且返回值还会被RecyclerView用于计算fling效果。
     */
    override fun scrollVerticallyBy(
        dy: Int,
        recycler: RecyclerView.Recycler?,
        state: RecyclerView.State?
    ): Int {
        //位移0,没有子view,不移动
        //位移0,没有子view,不移动
        if (dy == 0 && childCount == 0) {
            return 0
        }
        var realOffset = dy //实际的滑动距离,可能会在边界出被修复

        //边界修复的代码:
        //边界修复的代码:
        if (mVerticalOffset + realOffset < 0) { //上边界
            realOffset = -mVerticalOffset
        } else if (realOffset > 0) {
            //利用最后一个子View比较修正
            val lastChild = getChildAt(childCount - 1)
            if (getPosition(lastChild!!) == itemCount - 1) { //getPosition--> adapter数据里的位置
                val gap = height - paddingBottom - getDecoratedBottom(lastChild!!)
                realOffset = if (gap > 0) {
                    -gap
                } else if (gap == 0) {
                    0
                } else {
                    Math.min(realOffset, -gap)
                }
            }
        }
        realOffset = fill(recycler!!, state!!, realOffset) //先填充,再位移

        mVerticalOffset += realOffset //累加 实际滑动距离

        offsetChildrenVertical(-realOffset) //整体移动所有的childView

        return realOffset
    }

    override fun canScrollVertically(): Boolean = true

    /**
     * 获取某个childView 在水平方向所占用的空间
     *
     * @param view
     * @return
     */
    fun getDecoratedMeasurementHorizontal(view: View): Int {
        val params = view.layoutParams as RecyclerView.LayoutParams
        return getDecoratedMeasuredWidth(view) + params.leftMargin + params.rightMargin
    }

    /**
     * 获取某个childView在竖直方向所占用的空间
     *
     * @return
     */
    fun getDecoratedMeasurementVertical(view: View): Int {
        val params = view.layoutParams as RecyclerView.LayoutParams
        return getDecoratedMeasuredHeight(view) + params.topMargin + params.bottomMargin
    }

    /**
     * RecyclerView的
     * 水平方向 剩余空间 考虑了recyclerView的padding值
     *
     * @return
     */

    fun getHorizontalSpace(): Int = width - paddingLeft - paddingRight

    /**
     * RecyclerView的
     * 竖直方向 剩余空间 考虑了recyclerView的padding值
     *
     * @return
     */
    fun getVerticalSpace(): Int = height - paddingTop - paddingBottom

}